﻿#include "pch.h"
#include "qgp/qgach.h"
#include "qg_trace.h"
#include "qg_img.h"
#include "qg_nfo.h"
#include "extend/sbvalues.h"

//////////////////////////////////////////////////////////////////////////
// 디바이스 
KIMPL_SUBCLASS_ABSTRACT(qgRdh, "RenderDevice");

namespace _qgp
{
#if _QG_USE_DX9
	extern kType RdmTypeDX9();
#endif

#if _QG_USE_D11
	extern kType RdmTypeD11();
#endif

#if _QG_USE_GL
	extern kType RdmTypeGL();
#endif

	static kType DetectRdmType(const char* text, kint* flags)
	{
		static struct
		{
			const char* alias1;
			const char* alias2;
			const char* alias3;
			kType(*func)();
			kint flags;
			kint ossub;
		} lsts[] =
		{
#if _QG_USE_D11
			{"D11", "DX11", "DirectX11", &RdmTypeD11, QGSDF_BGRA, KSIPFS_WINDOWS_7},
#endif
#if _QG_USE_GL
			{"GL", "OGL", "OPENGL", &RdmTypeGL, QGSDF_INDP, 0},
#endif
			{"", "", "", NULL, 0, 0},
		};

		kint ossub = k_sisd_ossub();

		if (!text || !*text)
		{
			for (ksize_t i = 0; i < K_COUNTOF(lsts); i++)
			{
				if (lsts[i].func && ossub >= lsts[i].ossub)
				{
					*flags |= lsts[i].flags;
					return lsts[i].func();
				}
			}
		}
		else
		{
			for (ksize_t i = 0; i < K_COUNTOF(lsts); i++)
			{
				if ((k_strieqv(lsts[i].alias1, text) ||
					k_strieqv(lsts[i].alias2, text) ||
					k_strieqv(lsts[i].alias3, text)) &&
					ossub >= lsts[i].ossub)
				{
					*flags |= lsts[i].flags;
					return lsts[i].func();
				}
			}
		}

		return KTYPE_NULL;
	}
}

//
qgRdh* qgRdh::Instance = NULL;

//
qgRdh::qgRdh()
	: _lock()
{
	if (Instance == NULL)
		Instance = this;
}

//
qgRdh::~qgRdh()
{
	if (Instance == this)
		Instance = NULL;
}

//
const char* qgRdh::GetDeviceName()
{
	return type_info->name;
}

//
bool qgRdh::_Initialize(qgStub* stub, kint flags)
{
	// 여기서 POST INITIALIZE 역할을 함

	// 스텁
	_stub = stub;
	stub->Load();

	// 스터브 이미지 읽기/쓰기 등록
	_img::RegisterReaders(stub);
	_img::RegisterWriters(stub);

	// 준비
	return _ResetPrepare(stub->GetSize(), true);
}

//
bool qgRdh::_ResetPrepare(const tpl::Point& size, bool prepare)
{
	// 이 함수는 리셋 역할을 하지만
	// 처음에는 prepare=true 로 인해 최초 준비 역할을 함

	// TM
	float aspect = size.Aspect();

	for (ksize_t i = 0; i < QGTM_MAX_VALUE; i++)
		_prsn.t_tm[i].Identify();

	_prsn.t_tm[QGTM_PROJ] = tpl::Mat4::PerspectiveFovLH(KM_PI_HOH, aspect, 1.0f, 100000.0f);

	_prsn.t_vipr = _prsn.t_tm[QGTM_PROJ];
	_prsn.t_invv.Identify();

	_prsn.t_otm.Identify();
	_prsn.t_tfm.Identify();

	_prsn.t_sdm = size;

	_prsn.t_vp.loc = tpl::Point(0, 0);
	_prsn.t_vp.size = size;
	_prsn.t_vp.depth = tpl::Vec2(0.0f, 1.0f);
	_prsn.t_vp.aspect = aspect;

	// 색깔
	_prsn.cr_bg = tpl::Color(0.2f, 0.2f, 0.2f);

	// 준비할거 해야하네
	if (prepare)
	{
	}

#if 1
	// 설정없이 그려도 되도록 미리 설정함
	SetDhsl(qbStaticDhsl<>::GetPtr());
	SetRasz(qbStaticRasz<>::GetPtr());
	SetBlnd(qbStaticBlnd<>::GetPtr());

	for (kint i = 0; i < QGSGTEX_MAX_VALUE; i++)
		SetSplr(i, qbStaticSplr<>::GetPtr());	// 하나만 해도 될까?
#endif

	return true;
}

//
void qgRdh::_Finalize()
{
	// 스터브 해제
	if (_stub)
		_stub->Unload();
}

//
void qgRdh::SetPropVec4(kint n, const tpl::Vec4& v)
{
	k_return_if_fail((ksize_t)n < K_COUNTOF(_prsn.ps_v));
	_prsn.ps_v[n] = v;
}

//
void qgRdh::SetPropMat4(kint n, const tpl::Mat4& m)
{
	k_return_if_fail((ksize_t)n < K_COUNTOF(_prsn.ps_m));
	_prsn.ps_m[n] = m;
}

//
void qgRdh::SetPropWedge(kint cnt, const tpl::Mat4* pcb)
{
	_prsn.pw_cnt = cnt;
	_prsn.pw_pcb = pcb;
}

//
bool qgRdh::Run(kpointer osstate)
{
	if (!_stub->Run(osstate))
		return false;
	else
	{
		if (_stub->GetInsts(QGSDI_VIRTUAL))
		{
			// 가상 모드라면 스텁 디바이스 크기 확인
			_stub->Layout();
		}

		_nfo.invokes = 0;
		_nfo.creations = 0;
		_nfo.disposes = 0;
		_nfo.states = 0;
		_nfo.shaders = 0;
		_nfo.textures = 0;
		_nfo.renderers = 0;
		_nfo.transforms = 0;
		_nfo.drawcalls = 0;
		_nfo.vertices = 0;

		return true;
	}
}

//
bool qgRdh::Layout(kint width, kint height)
{
	kGuardSpinLock(_lock)
	{
		if (_prsn.t_sdm.width == (kuint)width && _prsn.t_sdm.height == (kuint)height)
		{
			// 변경할 필요 없음
			return false;
		}

		_prsn.t_sdm = tpl::Point(width, height);

		// 뷰포트
		SetViewport(0, 0, width, height, 0.0f, 1.0f);

		// TM 바꿈
		float aspect = (float)width / (float)height;
		SetTM(QGTM_PROJ, tpl::Mat4::PerspectiveFovLH(KM_PI_HOH, aspect, 1.0f, 100000.0f));
		SetTM(QGTM_VIEW, tpl::Matrices::Identity);
		SetTM(QGTM_WORLD, tpl::Matrices::Identity);
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////

//
bool qgRdh::Enter(kint clearance)
{
	RDHNFO_INVOKE();

	_nfo.flush = false;

	return true;
}

//
bool qgRdh::Leave()
{
	RDHNFO_INVOKE();

	_nfo.flush = true;

	return true;
}

//
bool qgRdh::Flush()
{
	RDHNFO_INVOKE();

	if (!_nfo.flush)
	{
		kobj_tracet(KLV_WARNING, KERR_INVAL, "call leave before flush.");
		Leave();
	}

	_nfo.frames++;

	return true;
}

//
void qgRdh::ClearMRTs(kint clearance, kint colorcount, const tpl::ColorValue* colors, float depth, kuint stencil)
{
}

//
void qgRdh::SetBackground(const tpl::ColorValue& color)
{
	RDHNFO_INVOKE();

	_prsn.cr_bg = tpl::Color(color);
}

//
bool qgRdh::SetTM(qgTmType tm, const tpl::Mat4& m)
{
	RDHNFO_INVOKE();
	RDHNFO_TRANSFORM();

	switch (tm)
	{
		case QGTM_WORLD:
			_prsn.t_tm[QGTM_WORLD] = m;
			break;

		case QGTM_VIEW:
			_prsn.t_invv = m.Inverse();
			_prsn.t_vipr = m * _prsn.t_tm[QGTM_PROJ];
			_prsn.t_tm[QGTM_VIEW] = m;
			break;

		case QGTM_PROJ:
			_prsn.t_vipr = _prsn.t_tm[QGTM_VIEW] * m;
			_prsn.t_tm[QGTM_PROJ] = m;
			break;

		default:
			if (tm >= QGTM_MAX_VALUE)
				return false;
			else
			{
				//kint n = (kint)(tm - QGTM_TEX_0);
				_prsn.t_tm[tm] = m;
			}
			break;
	}

	return true;
}

//
void qgRdh::SetViewport(kint x, kint y, kint w, kint h, float minz, float maxz)
{
	RDHNFO_INVOKE();
	_prsn.t_vp.loc.Set(x, y);
	_prsn.t_vp.size.Set(w, h);
	_prsn.t_vp.depth.Set(minz, maxz);
	_prsn.t_vp.aspect = _prsn.t_vp.size.Aspect();
}

//////////////////////////////////////////////////////////////////////////

//
void qgRdh::PrimitiveDraw(qgTopology tpg, kint verts, kint stride, kconstpointer data)
{
	RDHNFO_INVOKE();
	kpointer pvb;
	PrimitiveBegin(tpg, verts, stride, &pvb);
	memcpy(pvb, data, verts * stride);
	PrimitiveEnd();
}

//
void qgRdh::PrimitiveDrawIndexed(qgTopology tpg, kint verts, kint vertstride, kconstpointer vertdata, kint indices, kint indstride, kconstpointer inddata)
{
	RDHNFO_INVOKE();
	kpointer pvb, pib;
	PrimitiveBeginIndexed(tpg, verts, vertstride, &pvb, indices, indstride, &pib);
	memcpy(pvb, vertdata, verts * vertstride);
	memcpy(pib, inddata, indices * indstride);
	PrimitiveEndIndexed();
}

//////////////////////////////////////////////////////////////////////////

//
qgRdh* qgRdh::New(const char* dev, const tpl::Rect& bound, kint flags, const char* title, kpointer app)
{
	kType type = _qgp::DetectRdmType(dev, &flags);

	if (type == KTYPE_NULL)
	{
		k_tracet(KLV_ERROR, kTsb(Type()).GetName(), KERR_NOTYPE, "no valid renderer device found");
		return NULL;
	}

	//
	qgRdh* self = (qgRdh*)kCrbs::Create(type);

	if (self == NULL)
	{
		// 이유는 만들 때 내부에서 알려줌
		return NULL;
	}

	//
	qgStub* stub = qgStub::New(bound, title, app, flags);

	if (!self->_Initialize(stub, flags))
	{
		self->Unload();
		self = NULL;
	}

	stub->Unload();

	return self;
}

//
qgRdh* qgRdh::New(const char* dev, kint width, kint height, kint flags, const char* title, kpointer app)
{
	kType type = _qgp::DetectRdmType(dev, &flags);

	if (type == KTYPE_NULL)
	{
		k_tracet(KLV_ERROR, kTsb(Type()).GetName(), KERR_NOTYPE, "no valid renderer device found");
		return NULL;
	}

	//
	qgRdh* self = (qgRdh*)kCrbs::Create(type);

	if (self == NULL)
	{
		// 이유는 만들 때 내부에서 알려줌
		return NULL;
	}

	//
	qgStub* stub = qgStub::New(width, height, title, app, flags);

	if (!self->_Initialize(stub, flags))
	{
		self->Unload();
		self = NULL;
	}

	stub->Unload();

	return self;
}

//
qgRdh* qgRdh::NewVirtual(const char* dev, kpointer handle, kint flags)
{
	kType type = _qgp::DetectRdmType(dev, &flags);

	if (type == KTYPE_NULL)
	{
		k_tracet(KLV_ERROR, kTsb(Type()).GetName(), KERR_NOTYPE, "no valid renderer device found");
		return NULL;
	}

	//
	qgRdh* self = (qgRdh*)kCrbs::Create(type);

	if (self == NULL)
	{
		// 이유는 만들 때 내부에서 알려줌
		return NULL;
	}

	//
	qgStub* stub = qgStub::NewVirtual(handle);

	if (!self->_Initialize(stub, flags))
	{
		self->Unload();
		self = NULL;
	}

	stub->Unload();

	return self;
}

